package com.vhall.component.service.tag.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.base.Splitter;
import com.vhall.component.dao.account.AccountMapper;
import com.vhall.component.dao.room.RoomMapper;
import com.vhall.component.dao.tag.TagMapper;
import com.vhall.component.entity.account.AccountEntity;
import com.vhall.component.entity.inav.vo.TagVO;
import com.vhall.component.entity.room.dto.RoomsEntityDTO;
import com.vhall.component.entity.room.entity.RoomsEntity;
import com.vhall.component.entity.tag.TagEntity;
import com.vhall.component.entity.tag.vo.*;
import com.vhall.component.framework.common.exception.BusinessException;
import com.vhall.component.plugin.common.annotation.TokenAuth;
import com.vhall.component.plugin.common.constant.RedisKey;
import com.vhall.component.plugin.common.constant.TagConstant;
import com.vhall.component.plugin.common.exception.BizErrorCode;
import com.vhall.component.plugin.common.support.Token;
import com.vhall.component.plugin.common.support.TokenThreadLocal;
import com.vhall.component.plugin.common.utils.JsonUtil;
import com.vhall.component.plugin.core.service.PaasService;
import com.vhall.component.service.room.RoomInnerService;
import com.vhall.component.service.tag.TagService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ConvertingCursor;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author guoxin.wang
 * @date 2021-07-05 10:38
 */
@Slf4j
@Service
public class TagServiceImpl implements TagService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private TagMapper tagMapper;

    @Autowired
    private RoomInnerService roomInnerService;

    @Autowired
    private PaasService paasService;
    @Resource
    private AccountMapper accountMapper;
    @Resource
    private RoomMapper roomMapper;

    @Override
    public TagEntity save(TagCreateVO reqVO) {
        Token token = TokenThreadLocal.getToken();
        LambdaQueryWrapper<TagEntity> wrapper = Wrappers.lambdaQuery();
        if (TagConstant.TAG_TYPE_COMMON_USE.equals(reqVO.getType())) {
            wrapper.eq(TagEntity::getType, reqVO.getType());
            Integer count = tagMapper.selectCount(wrapper);
            if (count.equals(TagConstant.TAG_COMP_TAG_MAX_VALUE)) {
                throw new BusinessException(BizErrorCode.COMP_TAG_MAX_VALUE);
            }
        }
        wrapper.clear();
        wrapper.eq(TagEntity::getName, reqVO.getName());
        TagEntity tagEntityOfDb = tagMapper.selectOne(wrapper);
        if (tagEntityOfDb != null && tagEntityOfDb.getTagId() > 0) {
            // 更新
            throw new BusinessException(BizErrorCode.COMP_TAG_ALREADY_EXIST);
        } else {
            // 新增
            TagEntity param = new TagEntity();
            param.setName(reqVO.getName());
            param.setType(reqVO.getType());
            param.setAccountId(token.getAccountId());
            param.setAppId(paasService.getAppId());
            param.setCreatedAt(LocalDateTime.now());
            param.setUpdatedAt(LocalDateTime.now());
            int insert = tagMapper.insert(param);
            if (insert > 0) {
                return param;
            } else {
                return null;
            }
        }
    }

    @Override
    public TagPage<TagEntityVO> list(TagListVO reqVO) {
        reqVO.setCurrPage(reqVO.getCurrPage() == null ? 1 : reqVO.getCurrPage());
        reqVO.setPageSize(reqVO.getPageSize() == null ? 20 : reqVO.getPageSize());
        LambdaQueryWrapper<TagEntity> wrapper = Wrappers.lambdaQuery();
        Integer type = reqVO.getType();
        wrapper.eq(Objects.nonNull(type),TagEntity::getType, type);
        if (StringUtils.isNotBlank(reqVO.getName())) {
            wrapper.and(wrapper1 -> wrapper1.like(TagEntity::getName, reqVO.getName()).or().like(TagEntity::getTagId, reqVO.getName()));
        }
        wrapper.isNull(TagEntity::getDeletedAt);
        // 自定义标签--》0，公共标签--》1
        wrapper.orderByDesc(TagEntity::getTagId);
        Page<TagEntity> pageInit = new Page<>(reqVO.getCurrPage(), reqVO.getPageSize());
        pageInit = tagMapper.selectPage(pageInit, wrapper);
        List<TagEntity> records = pageInit.getRecords();
        List<TagEntityVO> data = buildTagListVO(records);


        Page<TagEntityVO> page = new Page(reqVO.getCurrPage(), reqVO.getPageSize(),pageInit.getTotal());
        page.setRecords(data);
        return new TagPage<>(page);
    }

    private List<TagEntityVO> buildTagListVO(List<TagEntity> records) {
        List<TagEntityVO> data = new ArrayList<>();
        if (CollUtil.isNotEmpty(records)) {
            List<Integer> accountIdList = records.stream().map(TagEntity::getAccountId).collect(Collectors.toList());
            LambdaQueryWrapper<AccountEntity> queryWrapper = Wrappers.lambdaQuery();
            queryWrapper.in(AccountEntity::getAccountId,accountIdList);
            List<AccountEntity> accountEntityList = accountMapper.selectList(queryWrapper);
            Map<Integer, List<AccountEntity>> accountIdToBeanMap = accountEntityList.stream().collect(Collectors.groupingBy(AccountEntity::getAccountId));
            data = records.stream().map(tag->{
                TagEntityVO temp = new TagEntityVO();
                BeanUtil.copyProperties(tag,temp);
                Integer accountId = tag.getAccountId();
                List<AccountEntity> accountEntityList1 = accountIdToBeanMap.get(accountId);
                if (CollUtil.isNotEmpty(accountEntityList1)) {
                    AccountEntity accountEntity = accountEntityList1.get(0);
                    temp.setCreateName(accountEntity.getNickname());
                }
                return temp;
            }).collect(Collectors.toList());
        }
        return data;
    }

    @Override
    public Integer update(TagUpdateVO reqVO) {
        TagEntity param = new TagEntity();
        QueryWrapper<TagEntity> where = new QueryWrapper<>();
        where.eq("name", reqVO.getName());
        where.last("limit 1");
        TagEntity entity = tagMapper.selectOne(where);
        if (null != entity) {
            throw new BusinessException(BizErrorCode.COMP_TAG_ALREADY_EXIST);
        }
        BeanUtils.copyProperties(reqVO, param);
        return tagMapper.updateById(param);
    }

    @Override
    public Integer delete(TagDeleteVO reqVO) {
        List<String> tagList = Splitter.on(",").splitToList(reqVO.getTagIds());
        checkDeleteTagList(tagList);
        LambdaQueryWrapper<TagEntity> wrapper = Wrappers.lambdaQuery();
        wrapper.in(TagEntity::getTagId, tagList);
        int delete = tagMapper.delete(wrapper);
        if (delete > 0) {
            deleteTagCache();
        }
        return delete;
    }

    /**
     * 删除 tag 检查
     * @param tagList
     */
    private void checkDeleteTagList(List<String> tagList) {
        LambdaQueryWrapper<RoomsEntity> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.isNull(RoomsEntity::getDeletedAt);
        for (String tagId : tagList) {
            queryWrapper.and(
                    StrUtil.isNotBlank(tagId),w->w.eq(RoomsEntity::getTopics,tagId)
                    .or().likeLeft(RoomsEntity::getTopics,","+tagId)
                    .or().likeRight(RoomsEntity::getTopics,tagId+",")
                    .or().like(RoomsEntity::getTopics,","+tagId+",")
            );
        }
        List<RoomsEntity> roomsEntities = roomMapper.selectList(queryWrapper);
        if (CollUtil.isNotEmpty(roomsEntities)) {
            BusinessException businessException = new BusinessException(BizErrorCode.COMP_TAG_EXIST_ROOM_LK);
            String ilIds = roomsEntities.stream().map(data -> data.getIlId().toString()).collect(Collectors.joining(","));
            businessException.setMsg("已被"+ilIds+"关联，无法删除，请取消关联后重试！");
            throw businessException;
        }
    }

    @SuppressWarnings("unchecked")
    private void deleteTagCache() {
        ScanOptions options = ScanOptions.scanOptions()
                .count(1000)
                .match(RedisKey.TAG_INFO_BY_IDS + "*").build();
        RedisSerializer<String> redisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();

        Cursor<Object> cursor = redisTemplate.executeWithStickyConnection(redisConnection ->
                new ConvertingCursor<>(redisConnection.scan(options), redisSerializer::deserialize));
        try {
            while (cursor.hasNext()) {
                String key = cursor.next().toString();
                redisTemplate.delete(key);
            }
        } catch (Exception e) {
            log.error("prefix cache delete failed,fail reason:{}", e.getMessage(), e);
        } finally {
            //关闭cursor
            try {
                cursor.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }


    @Override
    public List<TagEntity> getInfo(TagInfoVO reqVO) {
        List<TagEntity> result;
        Object redisValue = redisTemplate.opsForValue().get(RedisKey.TAG_INFO_BY_IDS + reqVO.getTagIds());
        if (redisValue == null) {
            List<String> tagList = Splitter.on(",").splitToList(reqVO.getTagIds());
            LambdaQueryWrapper<TagEntity> wrapper = Wrappers.lambdaQuery();
            wrapper.in(TagEntity::getTagId, tagList);
            List<TagEntity> tagEntities = tagMapper.selectList(wrapper);
            result = tagEntities;
            redisTemplate.opsForValue().set(RedisKey.TAG_INFO_BY_IDS + reqVO.getTagIds(), JsonUtil.toJsonString(tagEntities), 30, TimeUnit.MINUTES);
        } else {
            result = JsonUtil.arrayFromJSONString(redisValue.toString(), TagEntity.class);
        }
        return result;
    }

    @Override
    public List<TagVO> listByIds(String tagIds) {
        TagInfoVO vo = new TagInfoVO();
        vo.setTagIds(tagIds);


        List<TagEntity> entities = getInfo(vo);

        List<TagVO> tags = new ArrayList<>();
        if (null == entities) {
            return tags;
        }
        for (TagEntity tag : entities) {
            TagVO tagVO = new TagVO();
            tagVO.setName(tag.getName());
            tagVO.setTagId(tag.getTagId());
            tags.add(tagVO);
        }
        return tags;
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public Boolean rank(TagOrderVO reqVO) {
        List<String> tagList = Splitter.on(",").splitToList(reqVO.getTagIds());
        List<String> rankList = Splitter.on(",").splitToList(reqVO.getRank());
        for (int i = 0; i < tagList.size(); i++) {
            LambdaQueryWrapper<TagEntity> wrapper = Wrappers.lambdaQuery();
            wrapper.eq(TagEntity::getTagId, tagList.get(i));
            TagEntity tagEntity = tagMapper.selectOne(wrapper);
            if (tagEntity != null) {
                String rank = rankList.get(i);
                if (rank.length() > 9) {
                    throw new BusinessException(BizErrorCode.BIZ_NUM_IS_TOO_LONG);
                }
                tagEntity.setStatus(Integer.valueOf(rankList.get(i)));
                tagEntity.setUpdatedAt(LocalDateTime.now());
                tagMapper.updateById(tagEntity);
            } else {
                throw new BusinessException(BizErrorCode.COMP_TAG_NOT_EXIST);
            }
        }
        return true;
    }

    @Override
    public Map<Integer, String> tagsInfo(TagInfoVO reqVO) {
        Map<Integer, String> result = new HashMap<>(16);
        List<TagEntity> tes = getInfo(reqVO);
        for (TagEntity single : tes) {
            result.put(single.getTagId(), single.getName());
        }
        return result;
    }

    public Boolean getRealTagIds(Integer ilId, String tags) {
        RoomsEntityDTO roomInfoByIlId = roomInnerService.getRoomInfoByIlId(ilId.toString());
        if (roomInfoByIlId != null) {
            String topics = roomInfoByIlId.getTopics();
            if (StringUtils.isNotBlank(topics)) {
                List<String> liveTags = Splitter.on(",").splitToList(topics);
                List<String> tagss = Splitter.on(",").splitToList(tags);
                List<String> newArr = new ArrayList<>();
                for (String single : tagss) {
                    if (liveTags.contains(single)) {
                        newArr.add(single);
                    }
                }
                if (newArr.isEmpty()) {
                    return false;
                }
                String join = StringUtils.join(newArr, ",");
                TagCountVO param = new TagCountVO();
                param.setIds(join);
                count(param);
            }
        }
        return true;
    }

    @Override
    public Boolean count(TagCountVO param) {
        List<String> tagList = Splitter.on(",").splitToList(param.getIds());
        LambdaQueryWrapper<TagEntity> wrapper = Wrappers.lambdaQuery();
        wrapper.in(TagEntity::getTagId, tagList);
        List<TagEntity> info = tagMapper.selectList(wrapper);
        if (CollectionUtils.isEmpty(info)) {
            throw new BusinessException(BizErrorCode.COMP_TAG_NOT_EXIST);
        }
        if (CollectionUtils.isNotEmpty(info)) {
            List<Integer> ids = info.stream().map(TagEntity::getTagId).collect(Collectors.toList());
            tagMapper.incrementUseCountById(ids);
        }
        return true;
    }

    @Override
    public Boolean enableTag(EnableTagRequestVO vo) {
        LambdaQueryWrapper<TagEntity> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(TagEntity::getTagId,vo.getTagId()).last("limit 1");
        TagEntity tagEntity = Optional.ofNullable(tagMapper.selectOne(queryWrapper)).orElseThrow(()-> new BusinessException(BizErrorCode.EMPTY_TAG));
        Integer enable = vo.getEnable();
        tagEntity.setEnabled(enable);
        int update = tagMapper.updateById(tagEntity);
        return update>=1;
    }

}
